package com.huan.hystrix.hystrix;

import com.huan.hystrix.entity.Product;
import com.huan.hystrix.service.ProductService;
import com.netflix.hystrix.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Optional;

/**
 * 商品查询 hystrix command
 * <p>
 * 线程池隔离
 * <p>
 * 1、判断是否应该放行这个请求 com.netflix.hystrix.HystrixCircuitBreaker.HystrixCircuitBreakerImpl#allowRequest()
 *
 * @author huan.fu
 * @date 2018/8/8 - 15:35
 */
@Slf4j
public class SelectProductCommand extends HystrixCommand<Product> {
	private ProductService productService;
	private String productId;
	public SelectProductCommand(ProductService productService, String productId) {
		super(
				Setter
						// 配置全局唯一标识服务分组的名称 当我们进行监控时，相同分组的服务会聚合在一起，必填项
						.withGroupKey(HystrixCommandGroupKey.Factory.asKey("group-product"))
						// command key ，如果不进行配置，默认为类的 SimpleName，最好唯一
						.andCommandKey(HystrixCommandKey.Factory.asKey("command-selectOne(String)"))
						// 配置命令的一些参数
						.andCommandPropertiesDefaults(
								HystrixCommandProperties.Setter()
										// 是否启动超时处理  默认为 true
										.withExecutionTimeoutEnabled(true)
										// 超时时间为 10s
										.withExecutionTimeoutInMilliseconds(10000)
										// 使用线程池进行隔离
										.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
										// 隔离测试为 THREAD 时，执行线程执行超时时，是否进行中断处理 默认为 true
										.withExecutionIsolationThreadInterruptOnTimeout(true)
										// 当 Future#cancel(true) 是否进行中断处理  默认为 false
										.withExecutionIsolationThreadInterruptOnFutureCancel(true)

										// 打开短路器
										.withCircuitBreakerEnabled(true)
										// 如果在一个采样时间窗口内，失败率超过该配置，则自动打开熔断开关实现降级处理，即快速失败。默认配置下采样周期为10s，失败率为50%
										.withCircuitBreakerErrorThresholdPercentage(50)
										// 在短路器闭合情况下，在进行失败率判断之前，一个采样周期内必须进行至少N个请求才能进行采样统计，目的是有足够的采样使得失败率计算正确，默认为20
										.withCircuitBreakerRequestVolumeThreshold(60)
										// 短路器闭合的重试时间窗口，且在该时间窗口内只允许一次重试。即在熔断开关打开后，在该时间窗口允许有一次重试，如果重试成功，则将重置Health采样统计并闭合断路器开关实现快速恢复，否则断路器开关还是打开状态，执行快速失败。
										.withCircuitBreakerSleepWindowInMilliseconds(10000)
										// 是否强制关闭断路器，如果强制关闭则请求不会进行 fallback 处理
										.withCircuitBreakerForceClosed(false)
										// 是否强制打开短路器，如果打开了那么将会直接进行降级处理
										.withCircuitBreakerForceOpen(false)

										// 是否启用降级处理 默认是 true
										.withFallbackEnabled(true)
										// fallback方法的信号量配置，配置getFallback方法并发请求的信号量，如果请求超过了并发信号量限制，则不再尝试调用getFallback方法，而是快速失败，默认信号量为10
										.withFallbackIsolationSemaphoreMaxConcurrentRequests(100)

						)
						// 配置全局唯一标识线程池的名称 相同名称的线程池是同一个  如果不进行配置， 默认是 分组的名称
						.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("select-product-thread-pool"))
						// 线程池属性配置
						.andThreadPoolPropertiesDefaults(
								HystrixThreadPoolProperties.Setter()
										// 配置核心线程数的大小
										.withCoreSize(5)
										// 配置最大的线程数大小
										.withMaximumSize(10)
										// 允许最大线程数超过核心线程数，默认是 false ,如果这个值不为 true ,则上方配置的 withMaximumSize(10) 不会生效
										.withAllowMaximumSizeToDivergeFromCoreSize(true)
										// 线程池中空闲线程的生存时间
										.withKeepAliveTimeMinutes(5)
										// 配置线程池队列的最大大小
										.withMaxQueueSize(500)
										// 限制当前队列的大小，通过改变这个参数，可以实现动态改变队列的大小  当队列的大小超过 这个值 时会fallback
										.withQueueSizeRejectionThreshold(490)
						)
		);

		this.productService = productService;
		this.productId = productId;
	}

	@Override
	protected Product run() throws Exception {
		log.info("----> Run Thread Name:{}", Thread.currentThread().getName());
		return productService.selectOne(productId);
	}

	/**
	 * 1、最大并发数受 FallbackIsolationSemaphoreMaxConcurrentRequests 限制，当超过这个值时，不会进行 fallback 处理，直接失败
	 * 2、该方法最好不要进行网络访问，应该返回降级数据
	 * 3、如果必须要走网络请求，那么最好调用另外一个 Command 命令
	 *
	 * @return
	 */
	@Override
	protected Product getFallback() {
		log.info("----> FallBack Thread Name:{}", Thread.currentThread().getName());
		Optional.ofNullable(getFailedExecutionException()).ifPresent(exception -> log.error(exception.getMessage(), exception));
		return Product.builder().productName("服务降级").build();
	}
}
